
#include "SuspendedDustApp.hpp"
#include "Image.hpp" //


void SuspendedDustApp::updateFrame()
{
  m_camera->update();
  
  // --------
  
  // handles some events
  if (aer::EventHandler::IsKeyPressed( sf::Keyboard::Escape )) quit();
  if (aer::EventHandler::IsKeyPressed( sf::Keyboard::F )) printf("fps : %u\n", m_timer.getFPS());
}


void SuspendedDustApp::drawFrame()
{
  ///
  /// Important note : Order matters for Transform feedbacks :
  /// Program binding > TransformFeedback binding > TransformFeedback Begin
  ///
  
  glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
  
  
  /// 1) Update particles
  glEnable( GL_RASTERIZER_DISCARD );
  
  m_updatePgm.bind();
  CHECKGLERROR();
  
  // TF where the output will be redirected
  glBindTransformFeedback( GL_TRANSFORM_FEEDBACK, m_tf[m_dstBufferId]); CHECKGLERROR();
  glBeginTransformFeedback( GL_POINTS ); CHECKGLERROR();
    
    m_buffer[m_srcBufferId].bind();
    glVertexAttribPointer( 0, 3, GL_FLOAT, GL_FALSE, 0, 0);
    glEnableVertexAttribArray( 0 );

    if (m_isFirst) {
      glDrawArrays( GL_POINTS, 0, kNumParticles);
      m_isFirst = false;
    } else {
      glDrawTransformFeedback( GL_POINTS, m_tf[m_srcBufferId]);
    }
    
    glDisableVertexAttribArray( 0 ); 
    m_buffer[m_srcBufferId].unbind();
    
  glEndTransformFeedback();
  glBindTransformFeedback( GL_TRANSFORM_FEEDBACK, 0u);
  
  m_updatePgm.unbind();
  
  glDisable( GL_RASTERIZER_DISCARD );

  CHECKGLERROR();


/// 2) Render particles
  glDisable( GL_DEPTH_TEST );
  glEnable( GL_BLEND );
  
  glBlendEquation( GL_FUNC_ADD );
  glBlendFunc( GL_SRC_COLOR, GL_ONE_MINUS_SRC_ALPHA);
  //glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  
  m_texture.bind();
  m_renderPgm.bind();
  {
    aer::Matrix4x4 mvp = m_camera->getViewProjectionMatrix();
    m_renderPgm.setUniform( "uViewProjMatrix", mvp);
    m_renderPgm.setUniform( "uEyePosition", m_camera->getPosition());
    //m_renderPgm.setUniform( "uElapsedTime", 0.0f);
    m_renderPgm.setUniform( "uDiffuseTex", 0);
    
    m_buffer[m_dstBufferId].bind();
    glEnableVertexAttribArray( 0 );
    glVertexAttribPointer( 0, 3, GL_FLOAT, GL_FALSE, 0, 0);
    glEnableVertexAttribArray( 1 );
    glVertexAttribPointer( 1, 3, GL_FLOAT, GL_FALSE, 0, (void*)(3*sizeof(float)*kNumParticles));
    
    glDrawTransformFeedback( GL_POINTS, m_tf[m_dstBufferId]);
    
    glDisableVertexAttribArray(0);
    glDisableVertexAttribArray(1);
    
    m_buffer[m_dstBufferId].unbind();
  }
  m_renderPgm.unbind();
  m_texture.unbind();  
  
  glDisable( GL_BLEND );
  
  CHECKGLERROR();

  //
  m_srcBufferId = m_dstBufferId;
  m_dstBufferId = !m_dstBufferId;
  //
}


// --------------------------------------

void SuspendedDustApp::init()
{  
  /// Init the camera
  aer::View view( aer::Vector3( 0.0f, 0.0f, +30.0f),
                  aer::Vector3( 0.0f, 0.0f, 0.0f),
                  aer::Vector3( 0.0f, 1.0f, 0.0f) );
 
  float ratio = m_Window->getDisplay().getAspectRatio();
  aer::Frustum frustum( 60.0f, ratio, 0.1f, 500.0f);

  m_camera = new aer::FreeCamera( view, frustum);
  m_camera->setMoveCoefficient( 1.5f );
  

  /// OpenGL settings
  glClearColor( 0.0f, 0.0f, 0.0f, 1.0f);
    
  initGeometry();
  initTexture();
  initShader();
}


void SuspendedDustApp::initGeometry()
{  
  std::vector<aer::Vector3> positions(kNumParticles);
  std::vector<aer::Vector3> colors(kNumParticles);
  float bound = 15.0f;
    
  for (int i=0; i<kNumParticles; ++i)
  {
    aer::Vector3 &pos = positions[i];
    aer::Vector3 &col = colors[i];
    
    pos.x = bound * (rand()/float(RAND_MAX) - 0.5f) * 2.0f;
    pos.y = bound * (rand()/float(RAND_MAX) - 0.5f) * 2.0f;
    pos.z = bound * (rand()/float(RAND_MAX) - 0.5f) * 2.0f;
    
    col.x = rand()/float(RAND_MAX);
    col.y = col.x+0.2*(rand()/float(RAND_MAX)-0.5);
    col.z = col.x-0.2*(rand()/float(RAND_MAX));
  }
  
  
  // New particle with transform feedback for update  
  for (int i=0; i<2;++i)
  {
    aer::DeviceBuffer &buf = m_buffer[i];
    
    buf.generate();
    buf.bind( aer::DeviceBuffer::ARRAY );
    buf.resize( 2*3*sizeof(float)*kNumParticles );
    buf.setSubData( positions.data(), 3*sizeof(float)*positions.size());
    buf.setSubData( colors.data(), 3*sizeof(float)*colors.size(), 3*sizeof(float)*positions.size());
  
    glGenTransformFeedbacks( 1, &m_tf[i]);  
    glBindTransformFeedback( GL_TRANSFORM_FEEDBACK, m_tf[i]);
    glBindBufferBase( GL_TRANSFORM_FEEDBACK_BUFFER, 0, buf.getId());
  }
  m_buffer[0].unbind();
  glBindTransformFeedback( GL_TRANSFORM_FEEDBACK, 0u);
  
  m_srcBufferId = 0;
  m_dstBufferId = 1;
  m_isFirst = true;
  
  CHECKGLERROR();
}

void SuspendedDustApp::initTexture()
{ 
  Image_t img;//
    
  std::string texturePath( "../SuspendedDust/data/img/particle.png" );
  img.load( texturePath.c_str() );
  AER_CHECK( GL_TEXTURE_2D == img.target );
  
  m_texture.generate();
  m_texture.bind();
  m_texture.setWraps( aer::Texture::CLAMP_TO_BORDER, aer::Texture::CLAMP_TO_BORDER );
  
  m_texture.setImage(aer::Texture::TEXTURE_2D,
                     img.internalFormat, img.width, img.height, img.format, img.type,
                     img.data );
  
  glGenerateMipmap( GL_TEXTURE_2D ); //
  
  aer::Texture::Unbind( aer::Texture::TEXTURE_2D, 0u);
  
  CHECKGLERROR();
}

void SuspendedDustApp::initShader()
{
  /// GLSW, shader file manager
  glswInit();  
 
 
  const char *kShadersPath = "../SuspendedDust/data/shaders/"; 
  glswSetPath( kShadersPath, ".glsl");
  
  glswAddDirectiveToken("*", "#version 330 core");    
  
  
  m_renderPgm.generate();
    m_renderPgm.addShader( aer::Program::VERTEX_SHADER,   "Render.VS");
    m_renderPgm.addShader( aer::Program::GEOMETRY_SHADER, "Render.GS");
    m_renderPgm.addShader( aer::Program::FRAGMENT_SHADER, "Render.FS");
  AER_CHECK( m_renderPgm.link() );
  
  
  m_updatePgm.generate();
    m_updatePgm.addShader( aer::Program::VERTEX_SHADER,   "Update.VS");
    //m_updatePgm.addShader( aer::Program::GEOMETRY_SHADER, "Update.GS");
    
    const GLchar* varyings[] = { "outPosition" };
    glTransformFeedbackVaryings( m_updatePgm.getId(), AER_ARRAYSIZE(varyings), 
                                 varyings, GL_INTERLEAVED_ATTRIBS); 
  AER_CHECK( m_updatePgm.link() );
  
  
  glswShutdown();
  
  CHECKGLERROR();
}
